package com.yjc.drygoods.product.service.impl;

import com.yjc.drygoods.product.entity.SpuCommentEntity;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 核心算法
 *
 * @author tarzan
 * @version 1.0
 * @date 2020/7/31$ 15:21$
 * @since JDK1.8
 */
@Slf4j
@Service
public class CoreMath {

    public List<Long> recommend(String username, List<SpuCommentEntity> list) {
        //找到最近邻用户账户名
        Map<Double, String> distances = computeNearestNeighbor(username, list);
        if (distances != null) {//&&distances.size()>0
            String nearest = distances.values().iterator().next();

            Map<String, List<SpuCommentEntity>> userMap = list.stream().collect(Collectors.groupingBy(SpuCommentEntity::getUsername));

            //最近邻用户看过干货列表
            List<Long> neighborItemList = userMap.get(nearest).stream().map(e -> e.getSkuId()).distinct().collect(Collectors.toList());
//            log.info("neighborItemList:{}", neighborItemList);
            //指定用户看过干货列表
            List<Long> userItemList = userMap.get(username).stream().map(e -> e.getSkuId()).distinct().collect(Collectors.toList());
            ;
//            log.info("userItemList:{}", userItemList);
            //找到最近邻看过，但是该用户没看过的干货，计算推荐，放入推荐列表
            List<Long> recommendList = new ArrayList<>();
            for (Long item : neighborItemList) {
                if (!userItemList.contains(item)) {
                    recommendList.add(item);
                }
            }
            Collections.sort(recommendList);
            return recommendList;
        }
        return null;
    }


    /**
     * 在给定userId的情况下，计算其他用户和它的相关系数并排序
     *
     * @param username
     * @param list
     * @return
     */
    private Map<Double, String> computeNearestNeighbor(String username, List<SpuCommentEntity> list) {
        Map<String, List<SpuCommentEntity>> userMap = list.stream().collect(Collectors.groupingBy(SpuCommentEntity::getUsername));
        Map<Double, String> distances = new TreeMap<>();
        userMap.forEach((k, v) -> {
            if (!k.equals(username)) {
                double distance = pearson_dis(v, userMap.get(username));
                log.info("{}和指定用户的相关性系数：{}",k,distance);
                distances.put(distance, k);
            }
        });
        return distances;
    }


    /**
     * 计算两个序列间的相关系数
     *
     * @param xList
     * @param yList
     * @return
     */
    private double pearson_dis(List<SpuCommentEntity> xList, List<SpuCommentEntity> yList) {
        List<Integer> xs = Lists.newArrayList();
        List<Integer> ys = Lists.newArrayList();
        xList.forEach(x -> {
            yList.forEach(y -> {
                if (x.getSkuId() == y.getSkuId()) {
                    xs.add(x.getStar());
                    ys.add(y.getStar());
                }
            });
        });
        return getRelate(xs, ys);
    }

    /**
     * 方法描述: 皮尔森（pearson）相关系数计算
     *
     * @param xs
     * @param ys
     * @throws
     * @Return {@link Double}
     * @author tarzan
     * @date 2020年07月31日 17:03:20
     */
    public static Double getRelate(List<Integer> xs, List<Integer> ys) {
        int n = xs.size();
        double Ex = xs.stream().mapToDouble(x -> x).sum();
        double Ey = ys.stream().mapToDouble(y -> y).sum();
        double Ex2 = xs.stream().mapToDouble(x -> Math.pow(x, 2)).sum();
        double Ey2 = ys.stream().mapToDouble(y -> Math.pow(y, 2)).sum();
        double Exy = IntStream.range(0, n).mapToDouble(i -> xs.get(i) * ys.get(i)).sum();
        double numerator = Exy - Ex * Ey / n;
//        log.info("numerator:{}",numerator);
//        log.info("Ex2:{},(Ex2));
//        log.info("Ey2:{}",(Ey2));
//        log.info("(Ex2-Math.pow(Ex,2)/n):{}",(Ex2-Math.pow(Ex,2)/n));
//        log.info("(Ey2-Math.pow(Ey,2)/n):{}",(Ey2-Math.pow(Ey,2)/n));
        double denominator = Math.sqrt((Ex2 - Math.pow(Ex, 2) / n) * (Ey2 - Math.pow(Ey, 2) / n));
//        log.info("denominator:{}",denominator);
        if (denominator == 0) return 0.0;
        return numerator / denominator;
    }

}
